/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.dex.code;

import java.util.BitSet;

import com.android.dx.rop.code.RegisterSpec;
import com.android.dx.rop.code.RegisterSpecList;
import com.android.dx.rop.code.SourcePosition;
import com.android.dx.util.AnnotatedOutput;
import com.android.dx.util.Hex;
import com.android.dx.util.TwoColumnOutput;

/**
 * Base class for Dalvik instructions.
 */
public abstract class DalvInsn {

	/**
	 * the actual output address of this instance, if known, or {@code -1} if
	 * not
	 */
	private int address;

	/** the opcode; one of the constants from {@link Dops} */
	private final Dop opcode;

	/** {@code non-null;} source position */
	private final SourcePosition position;

	/** {@code non-null;} list of register arguments */
	private final RegisterSpecList registers;

	/**
	 * Makes a move instruction, appropriate and ideal for the given arguments.
	 * 
	 * @param position
	 *            {@code non-null;} source position information
	 * @param dest
	 *            {@code non-null;} destination register
	 * @param src
	 *            {@code non-null;} source register
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public static SimpleInsn makeMove(SourcePosition position,
			RegisterSpec dest, RegisterSpec src) {
		boolean category1 = dest.getCategory() == 1;
		boolean reference = dest.getType().isReference();
		int destReg = dest.getReg();
		int srcReg = src.getReg();
		Dop opcode;

		if ((srcReg | destReg) < 16) {
			opcode = reference ? Dops.MOVE_OBJECT : (category1 ? Dops.MOVE
					: Dops.MOVE_WIDE);
		} else if (destReg < 256) {
			opcode = reference ? Dops.MOVE_OBJECT_FROM16
					: (category1 ? Dops.MOVE_FROM16 : Dops.MOVE_WIDE_FROM16);
		} else {
			opcode = reference ? Dops.MOVE_OBJECT_16
					: (category1 ? Dops.MOVE_16 : Dops.MOVE_WIDE_16);
		}

		return new SimpleInsn(opcode, position,
				RegisterSpecList.make(dest, src));
	}

	/**
	 * Constructs an instance. The output address of this instance is initially
	 * unknown ({@code -1}).
	 * <p>
	 * <b>Note:</b> In the unlikely event that an instruction takes absolutely
	 * no registers (e.g., a {@code nop} or a no-argument no-result static
	 * method call), then the given register list may be passed as
	 * {@link RegisterSpecList#EMPTY}.
	 * </p>
	 * 
	 * @param opcode
	 *            the opcode; one of the constants from {@link Dops}
	 * @param position
	 *            {@code non-null;} source position
	 * @param registers
	 *            {@code non-null;} register list, including a result register
	 *            if appropriate (that is, registers may be either ins and outs)
	 */
	public DalvInsn(Dop opcode, SourcePosition position,
			RegisterSpecList registers) {
		if (opcode == null) {
			throw new NullPointerException("opcode == null");
		}

		if (position == null) {
			throw new NullPointerException("position == null");
		}

		if (registers == null) {
			throw new NullPointerException("registers == null");
		}

		this.address = -1;
		this.opcode = opcode;
		this.position = position;
		this.registers = registers;
	}

	/** {@inheritDoc} */
	@Override
	public final String toString() {
		StringBuffer sb = new StringBuffer(100);

		sb.append(identifierString());
		sb.append(' ');
		sb.append(position);

		sb.append(": ");
		sb.append(opcode.getName());

		boolean needComma = false;
		if (registers.size() != 0) {
			sb.append(registers.toHuman(" ", ", ", null));
			needComma = true;
		}

		String extra = argString();
		if (extra != null) {
			if (needComma) {
				sb.append(',');
			}
			sb.append(' ');
			sb.append(extra);
		}

		return sb.toString();
	}

	/**
	 * Gets whether the address of this instruction is known.
	 * 
	 * @see #getAddress
	 * @see #setAddress
	 */
	public final boolean hasAddress() {
		return (address >= 0);
	}

	/**
	 * Gets the output address of this instruction, if it is known. This throws
	 * a {@code RuntimeException} if it has not yet been set.
	 * 
	 * @see #setAddress
	 * @return {@code >= 0;} the output address
	 */
	public final int getAddress() {
		if (address < 0) {
			throw new RuntimeException("address not yet known");
		}

		return address;
	}

	/**
	 * Gets the opcode.
	 * 
	 * @return {@code non-null;} the opcode
	 */
	public final Dop getOpcode() {
		return opcode;
	}

	/**
	 * Gets the source position.
	 * 
	 * @return {@code non-null;} the source position
	 */
	public final SourcePosition getPosition() {
		return position;
	}

	/**
	 * Gets the register list for this instruction.
	 * 
	 * @return {@code non-null;} the registers
	 */
	public final RegisterSpecList getRegisters() {
		return registers;
	}

	/**
	 * Returns whether this instance's opcode uses a result register. This
	 * method is a convenient shorthand for {@code getOpcode().hasResult()} .
	 * 
	 * @return {@code true} iff this opcode uses a result register
	 */
	public final boolean hasResult() {
		return opcode.hasResult();
	}

	/**
	 * Gets the minimum distinct registers required for this instruction. Uses
	 * the given BitSet to determine which registers require replacement, and
	 * ignores registers that are already compatible. This assumes that the
	 * result (if any) can share registers with the sources (if any), that each
	 * source register is unique, and that (to be explicit here) category-2
	 * values take up two consecutive registers.
	 * 
	 * @param compatRegs
	 *            {@code non-null;} set of compatible registers
	 * @return {@code >= 0;} the minimum distinct register requirement
	 */
	public final int getMinimumRegisterRequirement(BitSet compatRegs) {
		boolean hasResult = hasResult();
		int regSz = registers.size();
		int resultRequirement = 0;
		int sourceRequirement = 0;

		if (hasResult && !compatRegs.get(0)) {
			resultRequirement = registers.get(0).getCategory();
		}

		for (int i = hasResult ? 1 : 0; i < regSz; i++) {
			if (!compatRegs.get(i)) {
				sourceRequirement += registers.get(i).getCategory();
			}
		}

		return Math.max(sourceRequirement, resultRequirement);
	}

	/**
	 * Gets the instruction that is equivalent to this one, except that it uses
	 * sequential registers starting at {@code 0} (storing the result, if any,
	 * in register {@code 0} as well).
	 * 
	 * @return {@code non-null;} the replacement
	 */
	public DalvInsn getLowRegVersion() {
		RegisterSpecList regs = registers.withExpandedRegisters(0, hasResult(),
				null);
		return withRegisters(regs);
	}

	/**
	 * Gets the instruction prefix required, if any, to use in an expanded
	 * version of this instance. Will not generate moves for registers marked
	 * compatible to the format by the given BitSet.
	 * 
	 * @see #expandedVersion
	 * @param compatRegs
	 *            {@code non-null;} set of compatible registers
	 * @return {@code null-ok;} the prefix, if any
	 */
	public DalvInsn expandedPrefix(BitSet compatRegs) {
		RegisterSpecList regs = registers;
		boolean firstBit = compatRegs.get(0);

		if (hasResult())
			compatRegs.set(0);

		regs = regs.subset(compatRegs);

		if (hasResult())
			compatRegs.set(0, firstBit);

		if (regs.size() == 0)
			return null;

		return new HighRegisterPrefix(position, regs);
	}

	/**
	 * Gets the instruction suffix required, if any, to use in an expanded
	 * version of this instance. Will not generate a move for a register marked
	 * compatible to the format by the given BitSet.
	 * 
	 * @see #expandedVersion
	 * @param compatRegs
	 *            {@code non-null;} set of compatible registers
	 * @return {@code null-ok;} the suffix, if any
	 */
	public DalvInsn expandedSuffix(BitSet compatRegs) {
		if (hasResult() && !compatRegs.get(0)) {
			RegisterSpec r = registers.get(0);
			return makeMove(position, r, r.withReg(0));
		} else {
			return null;
		}
	}

	/**
	 * Gets the instruction that is equivalent to this one, except that it
	 * replaces incompatible registers with sequential registers starting at
	 * {@code 0} (storing the result, if any, in register {@code 0} as well).
	 * The sequence of instructions from {@link #expandedPrefix} and
	 * {@link #expandedSuffix} (if non-null) surrounding the result of a call to
	 * this method are the expanded transformation of this instance, and it is
	 * guaranteed that the number of low registers used will be the number
	 * returned by {@link #getMinimumRegisterRequirement}.
	 * 
	 * @param compatRegs
	 *            {@code non-null;} set of compatible registers
	 * @return {@code non-null;} the replacement
	 */
	public DalvInsn expandedVersion(BitSet compatRegs) {
		RegisterSpecList regs = registers.withExpandedRegisters(0, hasResult(),
				compatRegs);
		return withRegisters(regs);
	}

	/**
	 * Gets the short identifier for this instruction. This is its address, if
	 * assigned, or its identity hashcode if not.
	 * 
	 * @return {@code non-null;} the identifier
	 */
	public final String identifierString() {
		if (address != -1) {
			return String.format("%04x", address);
		}

		return Hex.u4(System.identityHashCode(this));
	}

	/**
	 * Returns the string form of this instance suitable for inclusion in a
	 * human-oriented listing dump. This method will return {@code null} if this
	 * instance should not appear in a listing.
	 * 
	 * @param prefix
	 *            {@code non-null;} prefix before the address; each follow-on
	 *            line will be indented to match as well
	 * @param width
	 *            {@code >= 0;} the width of the output or {@code 0} for
	 *            unlimited width
	 * @param noteIndices
	 *            whether to include an explicit notation of constant pool
	 *            indices
	 * @return {@code null-ok;} the string form or {@code null} if this instance
	 *         should not appear in a listing
	 */
	public final String listingString(String prefix, int width,
			boolean noteIndices) {
		String insnPerSe = listingString0(noteIndices);

		if (insnPerSe == null) {
			return null;
		}

		String addr = prefix + identifierString() + ": ";
		int w1 = addr.length();
		int w2 = (width == 0) ? insnPerSe.length() : (width - w1);

		return TwoColumnOutput.toString(addr, w1, "", insnPerSe, w2);
	}

	/**
	 * Sets the output address.
	 * 
	 * @param address
	 *            {@code >= 0;} the output address
	 */
	public final void setAddress(int address) {
		if (address < 0) {
			throw new IllegalArgumentException("address < 0");
		}

		this.address = address;
	}

	/**
	 * Gets the address immediately after this instance. This is only calculable
	 * if this instance's address is known, and it is equal to the address plus
	 * the length of the instruction format of this instance's opcode.
	 * 
	 * @return {@code >= 0;} the next address
	 */
	public final int getNextAddress() {
		return getAddress() + codeSize();
	}

	/**
	 * Gets the size of this instruction, in 16-bit code units.
	 * 
	 * @return {@code >= 0;} the code size of this instruction
	 */
	public abstract int codeSize();

	/**
	 * Writes this instance to the given output. This method should never
	 * annotate the output.
	 * 
	 * @param out
	 *            {@code non-null;} where to write to
	 */
	public abstract void writeTo(AnnotatedOutput out);

	/**
	 * Returns an instance that is just like this one, except that its opcode is
	 * replaced by the one given, and its address is reset.
	 * 
	 * @param opcode
	 *            {@code non-null;} the new opcode
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public abstract DalvInsn withOpcode(Dop opcode);

	/**
	 * Returns an instance that is just like this one, except that all register
	 * references have been offset by the given delta, and its address is reset.
	 * 
	 * @param delta
	 *            the amount to offset register references by
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public abstract DalvInsn withRegisterOffset(int delta);

	/**
	 * Returns an instance that is just like this one, except that the register
	 * list is replaced by the given one, and its address is reset.
	 * 
	 * @param registers
	 *            {@code non-null;} new register list
	 * @return {@code non-null;} an appropriately-constructed instance
	 */
	public abstract DalvInsn withRegisters(RegisterSpecList registers);

	/**
	 * Gets the string form for any arguments to this instance. Subclasses must
	 * override this.
	 * 
	 * @return {@code null-ok;} the string version of any arguments or
	 *         {@code null} if there are none
	 */
	protected abstract String argString();

	/**
	 * Helper for {@link #listingString}, which returns the string form of this
	 * instance suitable for inclusion in a human-oriented listing dump, not
	 * including the instruction address and without respect for any output
	 * formatting. This method should return {@code null} if this instance
	 * should not appear in a listing.
	 * 
	 * @param noteIndices
	 *            whether to include an explicit notation of constant pool
	 *            indices
	 * @return {@code null-ok;} the listing string
	 */
	protected abstract String listingString0(boolean noteIndices);
}
